""" 统计方式对象模块

用于生成基础统计对象的类：
代码量（Cloc）、贡献者（GitBlame）、提交信息（GitShow）

用于生成统计最终报告的类：
报告生成类（Stats）
"""
import logging
import os
import re
import subprocess
from abc import ABCMeta, abstractmethod
from itertools import filterfalse

from code_metric.utils import retry, write_as_excel, read_excel_to_dict_list, run_command_write_result, clock
from code_metric.utils_str import form_abs_name_for_repo_cloc_raw_file, assemble_cloc_command_for_repo, \
    is_cloc_end_properly, \
    form_abs_name_for_version_code_line_report, form_abs_name_for_repo_cloc_excel_file, \
    form_abs_name_for_repo_blame_raw_file, \
    form_abs_name_for_repo_blame_excel_file, find_author_email_in_blame, \
    form_abs_name_for_version_author_code_line_report, \
    form_abs_name_for_version_group_code_line_report, is_line_contains_blame_info, \
    form_abs_name_for_commit_show_raw_file, \
    form_abs_name_for_commit_show_excel_file, is_line_contains_author_info, find_author_email_in_show, \
    is_line_contains_contribution_info, parse_git_show_info_to_add_del_file, parse_file_name_to_unified_path_name, \
    form_abs_name_for_commit_code_line_report, form_abs_name_for_repo_code_line_report, \
    form_abs_name_for_repo_author_line_report, form_abs_name_for_file_author_line_report


class Cloc:
    """play dumb，会仓库默认数据已经准备好了"""

    def __init__(self, repo_to_stat):
        self.repo = repo_to_stat
        self.cloc_raw_data_path = form_abs_name_for_repo_cloc_raw_file(repo_to_stat)
        self.cloc_excel_path = form_abs_name_for_repo_cloc_excel_file(repo_to_stat)

    def get_repo_cloc_txt(self, renew_now=False):
        if renew_now or not os.path.isfile(self.cloc_raw_data_path):
            self._generate_repo_cloc_data()
        return self.cloc_raw_data_path

    @retry(max_retry_times=3, retry_on_result=False)
    def _generate_repo_cloc_data(self):
        cmd = assemble_cloc_command_for_repo(self.repo.name)
        cwd = self.repo.clone_to_where
        output = self.cloc_raw_data_path
        last_line = run_command_write_result(cmd, cwd, output)
        return is_cloc_end_properly(last_line)

    def get_repo_cloc_excel(self, renew_now=False):
        if renew_now or not os.path.isfile(self.cloc_excel_path):
            self._generate_repo_cloc_excel(renew_now)
        return self.cloc_excel_path

    def _generate_repo_cloc_excel(self, renew_now):
        blank, comment, code = self._get_code_lines_count_from_cloc_raw_file(renew_now)
        code_sum = comment + code + blank
        code_count_list = [
            {"仓库链接": self.repo.url, "仓库名称": self.repo.name, "ref": self.repo.ref,
             "blank": blank, "comment": comment, "code": code, "code_sum": code_sum}]
        columns_order = ['仓库链接', '仓库名称', 'ref', 'blank', 'comment', 'code', 'code_sum']
        write_as_excel(self.cloc_excel_path, code_count_list, columns_order)

    # TODO：考虑从这里，对计算总代码量时，过滤不纳入计算的文件(目前依赖配置cloc命令行)
    def _get_code_lines_count_from_cloc_raw_file(self, renew_now):
        with open(self.get_repo_cloc_txt(renew_now), 'r', encoding='utf-8') as f:
            raw_data = f.read()
        if self.repo.name not in raw_data:
            return 0, 0, 0
        if 'SUM' not in raw_data:
            match = re.search(rf'^{self.repo.name}.+?\s*(\d+)\s*(\d+)\s*(\d+)\s*$', raw_data, re.M)
            return int(match.group(1)), int(match.group(2)), int(match.group(3))
        match = re.search(r'^SUM:\s*(\d+)\s*(\d+)\s*(\d+)\s*$', raw_data, re.M)
        return int(match.group(1)), int(match.group(2)), int(match.group(3))


class GitBlame:
    def __init__(self, repo_to_stat, file_to_stat):
        self.repo = repo_to_stat
        self.file = file_to_stat
        self.blame_raw_data_path = '\\\?\\' + form_abs_name_for_repo_blame_raw_file(repo_to_stat, file_to_stat)
        self.blame_excel_path = '\\\?\\' + form_abs_name_for_repo_blame_excel_file(repo_to_stat, file_to_stat)

    def get_repo_blame_txt(self, renew_now=False):
        if renew_now or not os.path.isfile(self.blame_raw_data_path):
            self._generate_repo_blame_data()
        return self.blame_raw_data_path

    def _generate_repo_blame_data(self):
        cmd = ['git', 'blame', '-e', self.file.path]
        cwd = self.repo.path
        output = self.blame_raw_data_path
        run_command_write_result(cmd, cwd, output, encoding='utf-8')

    def get_repo_blame_excel(self, renew_now=False):
        if renew_now or not os.path.isfile(self.blame_excel_path):
            self._generate_repo_blame_excel(renew_now)
        return self.blame_excel_path

    def _generate_repo_blame_excel(self, renew_now):
        author_count_dict = {}
        with open(self.get_repo_blame_txt(renew_now), 'r', encoding='utf-8', errors='ignore') as f:
            for line in f.readlines():
                if is_line_contains_blame_info(line):
                    email = find_author_email_in_blame(line)
                    author_count_dict[email] = author_count_dict.get(email, 0) + 1
        author_code_count_list = [
            dict(仓库名称=self.repo.name, 文件名称=self.file.name, 贡献者邮箱=email, 贡献行数=lines)
            for email, lines in author_count_dict.items()]
        if not author_code_count_list:
            logging.warning(f'仓库名称={self.repo.name}, 文件名称={self.file.name}, 无贡献者信息可记录')
        columns_order = ['仓库名称', '文件名称', '贡献者邮箱', '贡献行数']
        write_as_excel(self.blame_excel_path, author_code_count_list, columns_order)


class GitShow:
    def __init__(self, repo_to_stat, commit_to_stat):
        self.repo = repo_to_stat
        self.commit = commit_to_stat
        self.show_raw_data_path = '\\\?\\' + form_abs_name_for_commit_show_raw_file(repo_to_stat, commit_to_stat)
        self.show_excel_path = '\\\?\\' + form_abs_name_for_commit_show_excel_file(repo_to_stat, commit_to_stat)

    def get_commit_show_txt(self, renew_now=False):
        if renew_now or not os.path.isfile(self.show_raw_data_path):
            self._generate_commit_show_data()
        return self.show_raw_data_path

    def _generate_commit_show_data(self):
        cmd = ['git', 'show', self.commit.id, '--numstat', '--shortstat']
        cwd = self.repo.path
        output = self.show_raw_data_path
        run_command_write_result(cmd, cwd, output, encoding='utf-8')

    def get_commit_show_excel(self, renew_now=False):
        if renew_now or not os.path.isfile(self.show_excel_path):
            self._generate_commit_show_excel(renew_now)
        return self.show_excel_path

    def _generate_commit_show_excel(self, renew_now):
        contribution_count_list = []
        author_email = ''
        with open(self.get_commit_show_txt(renew_now), 'r', encoding='utf-8', errors='ignore') as f:
            for line in f.readlines():
                if is_line_contains_author_info(line):
                    author_email = find_author_email_in_show(line)
                    continue
                if is_line_contains_contribution_info(line):
                    insertions, deletions, file_name = parse_git_show_info_to_add_del_file(line)
                    file_name = parse_file_name_to_unified_path_name(self.repo.name, file_name)
                    contribution_count_list.append(
                        dict(仓库名称=self.repo.name, 文件名称=file_name, 贡献者邮箱=author_email,
                             增加行数=int(insertions), 减少行数=int(deletions)))
        columns_order = ['仓库名称', '文件名称', '贡献者邮箱', '增加行数', '减少行数']
        write_as_excel(self.show_excel_path, contribution_count_list, columns_order)


class ReportInfoHandler(metaclass=ABCMeta):
    @abstractmethod
    def is_repo_need_to_count(self, repo) -> bool:
        pass

    @abstractmethod
    def is_file_need_to_count(self, file) -> bool:
        pass

    @abstractmethod
    def is_author_need_to_count(self, author) -> bool:
        pass

    @abstractmethod
    def modify_repo_info(self, repo):
        pass

    @abstractmethod
    def modify_file_info(self, file):
        pass

    @abstractmethod
    def modify_author_info(self, author):
        pass


class DefaultRIHandler(ReportInfoHandler):
    def __init__(self, *args, **kwargs):
        pass

    def is_repo_need_to_count(self, repo) -> bool:
        return True

    def is_author_need_to_count(self, author) -> bool:
        return True

    def is_file_need_to_count(self, file) -> bool:
        return True

    def modify_repo_info(self, repo):
        pass

    def modify_file_info(self, file):
        pass

    def modify_author_info(self, author):
        pass


class Stats:

    def __init__(self, stat_handler: ReportInfoHandler = None,
                 version=None, version_code_report=None, version_author_report=None, version_group_report=None,
                 commit=None, commit_contribution_report=None,
                 repo=None, repo_code_report=None, repo_author_report=None,
                 file=None, file_author_report=None, ):
        self.stat_handler = DefaultRIHandler() if stat_handler is None else stat_handler
        self.version = version
        self.version_code_line_report = version_code_report
        self.version_author_line_report = version_author_report
        self.version_group_line_report = version_group_report
        self.commit = commit
        self.commit_contribution_report = commit_contribution_report
        self.repo = repo
        self.repo_code_report = repo_code_report
        self.repo_author_report = repo_author_report
        self.file = file
        self.file_author_report = file_author_report

    # TODO: 应该考虑分成不同的子类
    @classmethod
    def stat_version(cls, version_to_report, stat_handler=None):
        version_code_report = '\\\?\\' + form_abs_name_for_version_code_line_report(version_to_report)
        version_author_report = '\\\?\\' + form_abs_name_for_version_author_code_line_report(version_to_report)
        version_group_report = '\\\?\\' + form_abs_name_for_version_group_code_line_report(version_to_report)
        return cls(stat_handler=stat_handler, version=version_to_report, version_code_report=version_code_report,
                   version_author_report=version_author_report, version_group_report=version_group_report)

    @classmethod
    def stat_repo(cls, repo_to_report, stat_handler=None):
        repo_code_report = '\\\?\\' + form_abs_name_for_repo_code_line_report(repo_to_report)
        repo_author_report = '\\\?\\' + form_abs_name_for_repo_author_line_report(repo_to_report)
        return cls(stat_handler=stat_handler, repo=repo_to_report, repo_code_report=repo_code_report,
                   repo_author_report=repo_author_report)

    @classmethod
    def stat_file(cls, file_to_report, stat_handler=None):
        file_author_report = '\\\?\\' + form_abs_name_for_file_author_line_report(file_to_report)
        return cls(stat_handler=stat_handler, file=file_to_report, file_author_report=file_author_report)

    @classmethod
    def stat_commit(cls, commit_to_report, stat_handler=None):
        commit_contribution_report = form_abs_name_for_commit_code_line_report(commit_to_report)
        return cls(stat_handler=stat_handler, commit=commit_to_report,
                   commit_contribution_report=commit_contribution_report)

    def get_version_repos_code_line_report(self, renew_now=False):
        print(f'正在获取{self.version.owner_name}版本项目代码量统计报告...')
        if renew_now or not os.path.isfile(self.version_code_line_report):
            self._generate_version_repos_code_line_report(renew_now)
        return self.version_code_line_report

    def _generate_version_repos_code_line_report(self, renew_now):
        version_code_count_list = []
        repos_to_report = self._get_repo_need_to_count(renew_now)
        for idx, repo in enumerate(repos_to_report):
            print(f'目前{idx + 1}/{len(repos_to_report)}仓{repo.name}')
            self._gather_code_info_for_repo(repo, version_code_count_list, renew_now)
        self._output_code_info_to_excel(self.version_code_line_report, version_code_count_list)

    def get_version_repos_author_line_report(self, renew_now=False):
        print(f'正在获取{self.version.owner_name}版本贡献者代码量统计报告...')
        if renew_now or not os.path.isfile(self.version_author_line_report):
            self._generate_version_repos_author_line_report(renew_now)
        return self.version_author_line_report

    def _generate_version_repos_author_line_report(self, renew_now):
        version_author_count_list = []
        repos_to_report = self._get_repo_need_to_count(renew_now)
        for idx, repo in enumerate(repos_to_report):
            print(f'目前{idx + 1}/{len(repos_to_report)}仓{repo.name}')
            self._gather_author_info_for_repo(repo, version_author_count_list, renew_now)
        self._output_blame_author_info_to_excel(self.version_author_line_report, version_author_count_list)

    # TODO: 不是普遍行为，考虑解耦——重新设计 隔离变化
    def get_version_repos_group_line_report(self, renew_now=False):
        if renew_now or not os.path.isfile(self.version_group_line_report):
            self._generate_version_repos_group_line_report(renew_now)
        return self.version_group_line_report

    def _generate_version_repos_group_line_report(self, renew_now):
        code_sum = self._count_sum_code_for_version_repos(renew_now)
        code_huawei, code_partner, code_personal = self._count_group_code_for_version_repos(renew_now)
        code_3rd_part = code_sum - code_huawei - code_partner - code_personal
        version_group_count_list = [
            dict(时间=self.version.get_released_time(), 版本=self.version.name, 华为代码=code_huawei,
                 共建代码=code_partner, 个人贡献代码=code_personal, 三方代码=code_3rd_part, 总量=code_sum,
                 代码仓数量=len(self.version.get_repo_url_list()))]
        columns_order = ['时间', '版本', '华为代码', '共建代码', '个人贡献代码', '三方代码', '总量', '代码仓数量']
        write_as_excel(self.version_group_line_report, version_group_count_list, columns_order)

    def _count_sum_code_for_version_repos(self, renew_now):
        code_sum = 0
        code_line_list = read_excel_to_dict_list(self.get_version_repos_code_line_report(renew_now))
        for idx, project in enumerate(code_line_list):
            code_sum += project['code_sum']
        return code_sum

    def _count_group_code_for_version_repos(self, renew_now):
        code_huawei = code_partner = code_personal = 0
        author_line_list = read_excel_to_dict_list(self.get_version_repos_author_line_report(renew_now))
        for idx, repo_file in enumerate(author_line_list):
            author_email = repo_file['贡献者邮箱']
            company_name = repo_file['公司']
            code_count = repo_file['贡献行数']
            if author_email == 'not.committed.yet' and code_count == 1:
                continue
            if company_name == '华为':
                code_huawei += code_count
            elif company_name == '未知贡献者':
                code_personal += code_count
            else:
                code_partner += code_count
        return code_huawei, code_partner, code_personal

    def get_commit_contribution_line_report(self, renew_now=False):
        if renew_now or not os.path.isfile(self.commit_contribution_report):
            self._generate_commit_contribution_line_report(renew_now)
        return self.commit_contribution_report

    def _generate_commit_contribution_line_report(self, renew_now):
        commit_code_count_list = []
        self._gather_author_info_for_commit(self.commit, commit_code_count_list, renew_now)
        self._output_log_author_info_to_excel(self.commit_contribution_report, commit_code_count_list)

    def _gather_author_info_for_commit(self, commit, info_list, renew_now):
        contribution_list = self._get_author_need_to_count(commit, renew_now)
        for idx, contribution in enumerate(contribution_list):
            self.stat_handler.modify_file_info(contribution.file)
            commit_contribution_count_info = dict(仓库名称=self.commit.repo.name, 文件名称=contribution.file.name,
                                                  贡献者邮箱=contribution.email, 邮箱属主=contribution.get_email_host(),
                                                  公司=contribution.get_author_company(),
                                                  版权=contribution.file.get_copyright(),
                                                  增加行数=contribution.insert_lines,
                                                  减少行数=contribution.delete_lines)
            info_list.append(commit_contribution_count_info)

    def get_repo_code_line_report(self, renew_now=False):
        print('正在获取仓库代码量统计报告...')
        if renew_now or not os.path.isfile(self.repo_code_report):
            self._generate_repo_code_line_report(renew_now)
        return self.repo_code_report

    def _generate_repo_code_line_report(self, renew_now):
        repo_code_count_list = []
        self.stat_handler.modify_repo_info(self.repo)
        self._gather_code_info_for_repo(self.repo, repo_code_count_list, renew_now)
        self._output_code_info_to_excel(self.repo_code_report, repo_code_count_list)

    @staticmethod
    def _gather_code_info_for_repo(repo, info_list, renew_now):
        info_list.extend(read_excel_to_dict_list(Cloc(repo).get_repo_cloc_excel(renew_now)))

    def get_repo_author_line_report(self, renew_now=False):
        print('正在获取仓库贡献者代码量统计报告...')
        if renew_now or not os.path.isfile(self.repo_author_report):
            self._generate_repo_author_line_report(renew_now)
        return self.repo_author_report

    def _generate_repo_author_line_report(self, renew_now):
        repo_author_count_list = []
        self.stat_handler.modify_repo_info(self.repo)
        self._gather_author_info_for_repo(self.repo, repo_author_count_list, renew_now)
        self._output_blame_author_info_to_excel(self.repo_author_report, repo_author_count_list)

    def _gather_author_info_for_repo(self, repo, info_list, renew_now):
        file_to_report = self._get_file_need_to_count(repo, renew_now)
        for index, file in enumerate(file_to_report):
            print(f'目前{index + 1}/{len(file_to_report)}文件{file.name}')
            self._gather_author_info_for_file(file, info_list, renew_now)

    def get_file_author_line_report(self, renew_now=False):
        print('正在获取文件贡献者代码量统计报告...')
        if renew_now or not os.path.isfile(self.file_author_report):
            self._generate_file_author_line_report(renew_now)
        return self.file_author_report

    def _generate_file_author_line_report(self, renew_now):
        file_author_count_list = []
        self.stat_handler.modify_file_info(self.file)
        self._gather_author_info_for_file(self.file, file_author_count_list, renew_now)
        self._output_blame_author_info_to_excel(self.file_author_report, file_author_count_list)

    def _gather_author_info_for_file(self, file, info_list, renew_now):
        author_to_report = self._get_author_need_to_count(file, renew_now)
        for idx, author in enumerate(author_to_report):
            author_count_info = dict(仓库名称=file.repo.name, 文件名称=file.name, 版权=file.get_copyright(),
                                     贡献者邮箱=author.email, 邮箱属主=author.get_email_host(),
                                     公司=author.get_author_company(), 贡献行数=author.stocks_lines)
            info_list.append(author_count_info)

    def _get_repo_need_to_count(self, renew_now):
        repo_list = self.version.get_repo_object_list(renew_now)
        for repo in repo_list:
            self.stat_handler.modify_repo_info(repo)
        repo_to_report = list(filter(self.stat_handler.is_repo_need_to_count, repo_list))
        return repo_to_report

    def _get_file_need_to_count(self, repo, renew_now):
        file_list = repo.get_file_object_list(renew_now)
        for file in file_list:
            self.stat_handler.modify_file_info(file)
        file_to_report = list(filter(self.stat_handler.is_file_need_to_count, file_list))
        return file_to_report

    def _get_author_need_to_count(self, file, renew_now):
        author_list = file.get_contribution_obj_list(renew_now)
        for author in author_list:
            self.stat_handler.modify_author_info(author)
        author_list = list(filter(self.stat_handler.is_author_need_to_count, author_list))
        return author_list

    @staticmethod
    def _output_code_info_to_excel(file_name, info_list):
        columns_order = ['仓库链接', '仓库名称', 'ref', 'blank', 'comment', 'code', 'code_sum']
        write_as_excel(file_name, info_list, columns_order)

    @staticmethod
    def _output_blame_author_info_to_excel(file_name, info_list):
        columns_order = ['仓库名称', '文件名称', '版权', '贡献者邮箱', '邮箱属主', '公司', '贡献行数']
        write_as_excel(file_name, info_list, columns_order)

    @staticmethod
    def _output_log_author_info_to_excel(file_name, info_list):
        columns_order = ['仓库名称', '文件名称', '贡献者邮箱', '邮箱属主', '公司', '增加行数', '减少行数', '版权']
        write_as_excel(file_name, info_list, columns_order)


if __name__ == '__main__':
    pass
